package biolabs.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import biolabs.core.Application;
import biolabs.core.InterpreterResult;
import biolabs.gui.InputComponent.InputHandler;

/**
 * GUI component that displays the evaluated scripts and log messages.
 *
 * @author Tomas Mikula
 */
class ConsoleComponent implements UIComponent, InputHandler {

	/**
	 * Preferred size of the text pane.
	 */
	private static final Dimension PREFERRED_SIZE = new Dimension(400, 300);


	/**
	 * Attributes of the text copied verbatim from the input,
	 * i.e. the evaluated code.
	 */
	private static final SimpleAttributeSet INPUT = new SimpleAttributeSet();

	/**
	 * Text attributes of the result of script evaluation.
	 */
	private static final SimpleAttributeSet OUTPUT = new SimpleAttributeSet();

	/**
	 * Text attributes of an error message.
	 */
	private static final SimpleAttributeSet ERROR = new SimpleAttributeSet();

	/**
	 * Text attributes of an informative message.
	 */
	private static final SimpleAttributeSet INFO = new SimpleAttributeSet();

	/**
	 * Text attributes of a warning message.
	 */
	private static final SimpleAttributeSet WARNING = new SimpleAttributeSet();

	static {
		StyleConstants.setForeground(INPUT, Color.BLACK);
		StyleConstants.setForeground(OUTPUT, Color.BLUE);
		StyleConstants.setForeground(ERROR, Color.RED);
		StyleConstants.setForeground(INFO, Color.GRAY);
		StyleConstants.setForeground(WARNING, Color.ORANGE);
	}

	private final Application application;
	private final JTextPane textPane;
	private final StyledDocument doc;
	private final JScrollPane scrollPane;
	private final Handler logHandler;
	private final Logger logger;
	private final OutputStream consoleOut;
	private final OutputStream consoleErr;

	public ConsoleComponent(Application app) {

		doc = new DefaultStyledDocument();
		textPane = new JTextPane(doc);
		textPane.setEditable(false);
		textPane.setPreferredSize(PREFERRED_SIZE);
		scrollPane = new JScrollPane(textPane);

		consoleOut = new ConsoleOutputStream(INFO);
		consoleErr = new ConsoleOutputStream(WARNING);
		logHandler = createLogHandler();
		logger = Logger.getAnonymousLogger();
		logger.setUseParentHandlers(false);
		logger.addHandler(logHandler);

		application = app;
	}

	@Override
	public Component getContent() {
		return scrollPane;
	}

	@Override
	public synchronized void eval(String script) {
		if("".equals(script))
			return;
		try {
			appendText(script, INPUT);
			if(script.charAt(script.length()-1) != '\n')
				appendText("\n", null);
			InterpreterResult result = application.interpret(script);
			outputResult(result);
			scrollToEnd();
		} catch(RuntimeException e) {
			appendText("\n", null);
			logger.log(Level.SEVERE, "", e);
			throw e;
		} catch(Error e) {
			appendText("\n", null);
			logger.log(Level.SEVERE, "", e);
			throw e;
		}
	}

	private void outputResult(InterpreterResult result) {
		// first output chained previous result
		if(result.prev().isDefined())
			outputResult(result.prev().get());

		// output this result
		if(result.successful()) {
			appendText(result.output(), OUTPUT);
		} else {
			appendText(result.output(), WARNING);
		}
	}

	private void appendText(final String msg, final AttributeSet attr) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				try {
					doc.insertString(doc.getLength(), msg, attr);
				} catch (BadLocationException e) {
					throw new AssertionError(e);
				}
			}
		});
	}

	private void scrollToEnd() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				scrollPane.getViewport().scrollRectToVisible(new Rectangle(
						0, textPane.getPreferredSize().height, 0, 0));
			}
		});
	}

	public Handler getLogHandler() {
		return logHandler;
	}

	private Handler createLogHandler() {

		Handler handler = new Handler() {

			@Override
			public void close() throws SecurityException {
				// do nothing
			}

			@Override
			public void flush() {
				// do nothing
			}

			@Override
			public void publish(LogRecord record) {
				AttributeSet attr;
				int level = record.getLevel().intValue();
				if(level >= Level.SEVERE.intValue())
					attr = ERROR;
				else if(level >= Level.WARNING.intValue())
					attr = WARNING;
				else
					attr = INFO;
				appendText(getFormatter().format(record), attr);
				scrollToEnd();
			}

		};

		handler.setFormatter(new SimpleFormatter());
		return handler;
	}

	private class ConsoleOutputStream extends OutputStream {

		private final AttributeSet attr;

		public ConsoleOutputStream(AttributeSet attr) {
			this.attr = attr;
		}

		@Override
		public void write(int b) throws IOException {
			write(new byte[]{Integer.valueOf(b).byteValue()});
		}

		@Override
		public void write(byte[] b) {
			write(b, 0, b.length);
		}

		@Override
		public void write(byte[] b, int off, int len) {
			appendText(new String(b, off, len), attr);
		}
	}

	public OutputStream getOutputStream() {
		return consoleOut;
	}

	public OutputStream getErrorStream() {
		return consoleErr;
	}

}
